﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Xml;
using System.IO;
using System.Windows.Forms;

namespace fes
{

    

    #region Neuron
    public class Neuron : fes.INeuron 
    {
        private List<double> Inputs;
        private List<double> Weights;
        private int inputsNum;
        private double _LrnSpeed;
        public double LrnSpeed
        {
            get {return _LrnSpeed;}
            set {_LrnSpeed = value; }
        }
        private int Error;
        public double ErrorRate;
        private double _Output;
        public double Output
        {
            get{return _Output;}
            private set { _Output = value; }
        }
        

        /// <summary>
        ///    Constructs new neuron with defined number of inputs
        /// </summary>
        /// <param name="inputsNumber">Number of inputs</param>
        public Neuron(int inputsNumber)
        {
            Inputs = new List<double>(inputsNumber);
            Weights = new List<double>(inputsNumber);
           Output = 0;
           LrnSpeed = 5.0;
           inputsNum = inputsNumber;
           for (int i = 0; i < inputsNumber; i++) 
           {
               Inputs.Add(0.0);
               Weights.Add(0.0);
           }
        }

        /// <summary>
        ///    Constructs new neuron with defined number of inputs,
        ///    and randomizes the weights as a starting state, if
        ///    param randomizeWeights is set to true
        /// </summary>
        /// <param name="inputsNumber">Number of inputs</param>
        /// <param name="randomizeWeightsStart">lower bound for the ranomizer</param>
        /// <param name="randomizeWeightsEnd">upper bound for the ranomizer</param>
        public Neuron(int inputsNumber, double randomizeWeightsStart,double randomizeWeightsEnd)
        {
            Inputs = new List<double>(inputsNumber);
            Weights = new List<double>(inputsNumber);
           Output = 0;
           LrnSpeed = 5.0;
           inputsNum = inputsNumber;
           for (int i = 0; i < inputsNumber; i++) 
           {
               Inputs.Add(0.0);
               Weights.Add(0.0);
           }
           RandomizeWeights(randomizeWeightsStart,randomizeWeightsEnd);
        }

        /// <summary>
        ///  Randomizes weights
        /// </summary>
        /// <param name="s">lower bound</param>
        /// <param name="e">upper bound</param>
        public void RandomizeWeights(double s,double e)
        {
            Random r = new Random(OurGlobals.RandomSeed++);
            for (int i = 0; i < inputsNum; i++) 
           {
               Weights[i] = s+(double)r.NextDouble()*(e-s);
           }
        }

        /// <summary>
        ///  Constructor of a new neuron
        /// </summary>
        public Neuron()
        {
           Inputs = new List<double>();
           Weights = new List<double>();
           Output = 0;
           LrnSpeed = 0.5;
           inputsNum = 0;
        }

        /// <summary>
        ///  Returns the number of inputs
        /// </summary>
        /// <returns>Number of inputs</returns>
        public int NumberOfInputs 
        {  
            get 
            {
               return inputsNum; 
            }
        }

        public double GetWeight(int inputNumber)
        {
           if((inputNumber >= inputsNum) || (inputNumber <0)) return 0.0;
           return Weights[inputNumber];
        }

        public List<double> GetWeights()
        {
            return Weights;
        }

        /// <summary>
        ///   Adds an input to the neuron
        /// </summary>
        /// <param name="inputVal">The initial value on the input</param>
        /// <param name="weightVal">The initial weight</param>
        /// <returns></returns>
        public int InputAdd(double inputVal, double weightVal)
        {
           Inputs.Add(inputVal);
           Weights.Add(weightVal);
           inputsNum++;
           return inputsNum;
        }

        /// <summary>
        ///  Deletes an input of the specified number
        /// </summary>
        /// <param name="num">The number (index) of the input</param>
        /// <returns>false if input 'num' doesn't exist</returns>
        public bool InputDelete(int num)
        {
            if (num >= inputsNum) return false;
           Inputs.RemoveAt(num);
           Weights.RemoveAt(num);
           inputsNum--;
           return true;
        }

        /// <summary>
        ///   Changes the value of one of the inputs
        /// </summary>
        /// <param name="inputNum">The index of the input</param>
        /// <param name="newVal">New value for the input</param>
        /// <returns>false if input 'inputNum' doesn't exist</returns>
        public bool InputChange(int inputNum, double newVal)
        {
            if (inputsNum < inputNum) return false;
           Inputs[inputNum] = newVal;
            return true;
        }

        /// <summary>
        ///  Changes the weigt for the specified input
        /// </summary>
        /// <param name="weightNum">The index of the input for the weight to change</param>
        /// <param name="newVal">New value of weight</param>
        /// <returns></returns>
        public bool WeightChange(int weightNum, double newVal)
        {
            if (inputsNum < weightNum) return false;
           Weights[weightNum] = newVal;
            return true;
        }

        /// <summary>
        ///  Changes in bulk all the weights. The param must have the same number of values as the number of inputs specified.
        /// </summary>
        /// <param name="weightsList">Array of double numbers specifying the weights</param>
        /// <returns>false if the element count in the weightsList array is different than the number of inputs</returns>
        public bool WeightChange(params double[] weightsList)
        {
            if (weightsList.Length != inputsNum) return false;
           for (int i = 0; i < inputsNum; i++) Weights[i] = weightsList[i];
            return true;
        }

        /// <summary>
        ///  Changes in bulk all the input values. The param must have the same number of values as the number of inputs specified.
        /// </summary>
        /// <param name="weightsList">Array of double numbers specifying the inputs</param>
        /// <returns>false if the element count in the inputsList array is different than the number of inputs</returns>
        public bool InputChange(params double[] inputsList)
        {
            if (inputsList.Length != inputsNum) return false;
            for (int i = 0; i < inputsNum; i++) Inputs[i] = inputsList[i];
            return true;
        }

        /// <summary>
        ///  Sets the input and uses the Teach(double,double) method
        /// </summary>
        /// <param name="inputsList">array of doubleing point values for input (count must be equal to inputs number)</param>
        /// <param name="expLower">the lower bound of the right answer for the given input</param>
        /// <param name="expUpper">the upper bound of the right answer for the given input</param>
        /// <returns>The error of the calculation</returns>
        public int Teach(double expLower,double expUpper,params double[] inputsList) 
        {
            if (!InputChange(inputsList)) return -1; ;
            return Teach(expLower,expUpper);    

        }

        /// <summary>
        /// Calculate output for given input, but without changing the weights.
        /// </summary>
        /// <param name="inputsList">array of double - values of input</param>
        /// <returns>output</returns>
        public double Stimulate(params double[] inputsList)
        {
            if (!InputChange(inputsList)) return 0.0 ;
            return CalcOutput();
        
        }

        /// <summary>
        ///  Calculates the output and changes weights using a modified Hebb learning law
        /// </summary>
        /// <param name="expLower">the lower bound of the right answer for the given input</param>
        /// <param name="expUpper">the upper bound of the right answer for the given input</param>
        /// <returns>The error of the calculation</returns>
        public int Teach(double expLower, double expUpper)
        {
            double ret = CalcOutput();
            double LrnSpeedLocal = LrnSpeed;
            if ((ret <= expUpper) && (ret >= expLower))
            {
                Error = 0;
            }
            else
            {
                if (ret > expUpper)
                {
                    if (Error == 1) LrnSpeedLocal = LrnSpeed * (ret - expUpper) / 2;
                    Error = 1;
                }
                else if (ret < expLower)
                {
                    if (Error == -1) LrnSpeedLocal = LrnSpeed * (ret - expUpper) / 2;
                    Error = -1;
                }
                
                for (int i = 0; i < inputsNum; i++)
                {
                    Weights[i] = Weights[i] + LrnSpeedLocal * Error * Inputs[i]; //* ret;
                }
            }

            return Error ;

        }

        /// <summary>
        /// Teaching function for neural network purpose. It changes weights if the error is given
        /// </summary>
        /// <param name="ErrorState">The global error of the network</param>
        /// <returns>error</returns>
        public int Teach(int ErrorState)
        {

                for (int i = 0; i < inputsNum; i++)
                {
                    Weights[i] = Weights[i] + LrnSpeed * Inputs[i] * ErrorState * Output;
                }
                Error = ErrorState;
                return ErrorState;

        }

        public int TeachPropagation(int ErrorState)
        {
            double deriv = CalcOutputDerivative();
            for (int i = 0; i < inputsNum; i++)
            {
                Weights[i] = Weights[i] + LrnSpeed * Inputs[i] * ErrorState * ErrorRate * deriv;
            }
            Error = ErrorState;
            return ErrorState;

        }

        /// <summary>
        ///  Calculates the output for the transfer function for the actual set
        ///  of inputs and weights. 
        ///  For the weight*input sum < 0 y = -1 + 1/1-x
        ///  For the weight*input sum >= 0 y = 1- 1/1+x
        /// </summary>
        /// <returns>the calculation result</returns>
        private double CalcOutput() 
        {
            double ret = 0;
            for (int i = 0; i < inputsNum; i++) 
            {
                ret += Inputs[i] * Weights[i];
            }

            if (ret < 0) ret = -1.0 + (1.0 / (1.0 - ret));
            else ret = 1.0 - (1.0 / (1.0 + ret));

            Output = ret;
            return ret;
        }

        private double CalcOutputDerivative()
        {
            double ret = 0;
            for (int i = 0; i < inputsNum; i++)
            {
                ret += Inputs[i] * Weights[i];
            }
            if (ret < 0) ret = 1 / (ret * ret - 2 * ret + 1);
            else ret = 1 / (ret * ret + 2 * ret + 1);

            return ret;
        }

        /// <summary>
        ///  Prints to the Console control data
        /// </summary>
        public void controlWrite() 
        {
            if (inputsNum == 0) 
            {Console.WriteLine("Neuron doesn't have inputs defined. Use the InputAdd method to add inputs."); return;}

            for (int i = 0; i < inputsNum; i++) 
            {
                Console.WriteLine("{0} : {1} / {2} ", i, Inputs[i], Weights[i]);
            }
        }
    }

    #endregion

    #region NeuralGen

    class NeuralGen : IComparable
    {
        private int inputsNum;
        public int getInputs{get {return inputsNum;}}
        private double [] weightVector;
        private double _Output;
        public int position;
        public double Output { get { return _Output; } set { _Output = value; } }

        public NeuralGen(int inputs) 
        {
            inputsNum = inputs;
            _Output = 0.0;
            weightVector = new double[inputs];
        }

        public void RandomizeWeights(double s, double e)
        {
         Random r = new Random();
            for (int i = 0; i < inputsNum; i++) 
           {
               weightVector[i] = s+(double)r.NextDouble()*(e-s);
           }
        }

        public double [] getWeights()
        {
           return weightVector;
        }

        public double getWeights(int w)
        {
            if((w <0) || (w >= inputsNum)) return double.NaN;
            return weightVector[w];
        }


        public void setWeights(int from,int to, params double [] weights)
        {
           for(int i=from;i<to;i++)
           {
               if(i<inputsNum)   weightVector[i] = weights[i-from];
           }
        }

         public void setWeights(params double [] weights)
        {
           for(int i=0;i<weights.Length;i++)
           {
               if(i<inputsNum)   weightVector[i] = weights[i];
           }
        }

         public bool setWeights(int w,double val)
         {
             if((w <0) || (w >= inputsNum)) return false;
             weightVector[w] = val;
             return true;
         }

        public static bool operator <(NeuralGen ng1, NeuralGen ng2)
        {

            return ng1.Output < ng2.Output;

        }

        public static bool operator >(NeuralGen ng1, NeuralGen ng2)
        {

            return ng1.Output > ng2.Output;

        }

        public static bool operator ==(NeuralGen ng1, NeuralGen ng2)
        {
            if (ng1.Output == ng2.Output) return true;
            return false;

        }

        public static bool operator !=(NeuralGen ng1, NeuralGen ng2)
        {

            if (ng1.Output != ng2.Output) return true;
            return false;

        }

        public int CompareTo(object obj)
        {
            NeuralGen u = (NeuralGen)obj;
            return  this.Output.CompareTo(u.Output);
        }

         public static NeuralGen operator +(NeuralGen ng1, NeuralGen ng2)
        {
             NeuralGen n = new NeuralGen(ng1.getInputs);
             int rnd;
             if ((ng1.position > 0) && (ng1.position < ng1.getInputs))
                 rnd = ng1.position;
             else
             {
                 Random r = new Random();
                 rnd = r.Next(1, ng1.getInputs);
             }
             for (int i = 0; i < n.getInputs;i++ )
             {
                 if (i < rnd)
                     n.setWeights(i, ng1.getWeights(0));
                 else
                     n.setWeights(i, ng2.getWeights(i - rnd));
             }
             return n;
        }

         public static NeuralGen operator +(NeuralGen ng1, int position)
         {
             ng1.position = position;
             return ng1;
         }


    }

    #endregion

    #region NeuralNet
    public class NeuralNet
    {
        private List<Neuron> [] Net;
        private double [] Inputs;
        private double [] Outputs;
        private int _numOfOutputs;
        private int _numOfInputs;
        private int _numOfLayers;
        private int _numOfInputsGlobal;
        private int actNumOfLayers;
        private int actNumOfInputValues;
        //genetic
        private List<NeuralGen> GeneticPopulation;
        private int best;
        private int population;
        //genetic end
        public int numOfOutputs { get { return _numOfOutputs; } }
        public int numOfInputs { get { return _numOfInputs; } }
        public int numOfLayers { get { return _numOfLayers; } }
        public int numOfInputsGlobal { get { return _numOfInputsGlobal; } }
        private bool ready;

        /// <summary>
        /// Constructor for a specified network with given number of inputs and layers
        /// </summary>
        /// <param name="numOfInputs">number of inputs</param>
        /// <param name="numOfLayers">number of layers</param>
        public NeuralNet(int numOfInputs, int numOfLayers)
        {
            Net = new List<Neuron>[numOfLayers];
            Inputs = new double[numOfInputs];
            for (int i = 0; i < numOfLayers; i++)
            {
                Net[i] = new List<Neuron>();
            }
            this._numOfInputs = numOfInputs;
            this._numOfLayers = numOfLayers;
            this.actNumOfLayers = 0;
            this._numOfOutputs = 0;
            ready = false;
        
        }

        /// <summary>
        /// Constructor for a defined network. The format must be an XML file
        /// 
        /// </summary>
        /// <param name="filepath"></param>
        public NeuralNet(String filepath) 
        {
            ready = LoadFromFile(filepath);
        }

        /// <summary>
        /// Saves the current configuration and weights state of the network
        /// </summary>
        /// <param name="filepath"> xml file to save in</param>
        /// <returns>true, if everythings ok ;)</returns>
        public bool SaveNeuralNet(String filepath) 
        {
            if (!ready)
            {
                return false;
            }
            XmlWriterSettings settings = new XmlWriterSettings();
            settings.Indent = true;
            settings.IndentChars = ("    ");

            try
            {

                using (XmlWriter writer = XmlWriter.Create(filepath, settings))
                {
                    writer.WriteStartElement("neuralnet");
                    writer.WriteAttributeString("inputs", numOfInputs.ToString());
                    writer.WriteAttributeString("layers", numOfLayers.ToString());
                    for (int i = 0; i < numOfLayers; i++)
                    {
                        writer.WriteStartElement("layer");
                        writer.WriteAttributeString("neurons", Net[i].Count.ToString());
                        for (int j = 0; j < Net[i].Count; j++)
                        {
                            writer.WriteStartElement("neuron");
                            for (int k = 0; k < Net[i][j].NumberOfInputs; k++)
                            {
                                writer.WriteElementString("weight", Net[i][j].GetWeight(k).ToString());
                            }
                            writer.WriteEndElement(); //neurons
                        }
                        writer.WriteEndElement(); //layer
                    }
                    writer.WriteEndElement(); //neuralnet
                    writer.Close();
                }
                
                return true;
            }
            catch (Exception ex) 
            {
                MessageBox.Show("Wystąpił błąd podczas zapisu konfiguracji sieci do pliku. Można zapisać konfigurację ręcznie.\r\n Dosłowna treść wyjątku: "+ex.Message, "Błąd przy zapisie", MessageBoxButtons.OK, MessageBoxIcon.Stop);
                return false;
            }
        }

        /*
         * XML file forma for the network conf:
         * 
         *<?xml version="1.0" encoding="utf-8"?>
         *<neuralnet inputs="mainNetworkInputsNumber" layers="NumberOfLayers">
         *   <layer neurons="NumberOfNeuronsInTheLayer">
         *      <neuron>
         *         <weight>valueOfWeightForTheFirstInput</weight>
         *         <weight>valueOfWeightForTheSecondInput</weight>
         *         <weight>valueOfWeightForTheThirdInput</weight>
         *      </neuron>
         *      ...
         *      ... remaining neurons
         *      ...
         *   </layer>
         *   <layer ....>
         *   ...
         *   ... remaining layers
         *   </layer>
         *</neuralnet>
         * 
         */


        /// <summary>
        /// Loads the configuration of the network from a file
        /// </summary>
        /// <param name="filepath">XML file with a specified format</param>
        /// <returns>true, if everythings ok</returns>
        public bool LoadFromFile(String filepath)
        {
            if (!File.Exists(filepath))
            {
                this._numOfInputs = 0;
                this._numOfLayers = 0;
                this.actNumOfLayers = 0;
                this._numOfOutputs = 0;
                MessageBox.Show("Neural network file doesn't exist");
                return false;
            }

            try
            {
                XmlReaderSettings settings = new XmlReaderSettings();
                settings.IgnoreComments = true;
                settings.IgnoreWhitespace = true;
                settings.IgnoreProcessingInstructions = true;

                XmlReader reader = XmlReader.Create(filepath, settings);


                reader.ReadToFollowing("neuralnet");
                if ((!reader.HasAttributes) || (reader.AttributeCount != 2)) return false;
                _numOfInputs = Convert.ToInt32(reader.GetAttribute("inputs"));
                _numOfLayers = Convert.ToInt32(reader.GetAttribute("layers"));

                Net = new List<Neuron>[_numOfLayers];
                Inputs = new double[_numOfInputs];
                for (int i = 0; i < _numOfLayers; i++)
                {
                    Net[i] = new List<Neuron>();
                }
                this.actNumOfLayers = 0;

                int neuronsN;
                int inputsN;
                int all=0;
                reader.Read();
                for (int j = 0; j < _numOfLayers; j++)
                {
                    //reader.ReadStartElement("layer");

                    if (!reader.HasAttributes) return false;

                    if (j == 0) inputsN = _numOfInputs;
                    else inputsN = Net[j - 1].Count;
                    neuronsN = Convert.ToInt32(reader.GetAttribute("neurons"));
                    double[] w = new double[neuronsN * inputsN];
                    all += neuronsN * inputsN;
                    reader.Read();
                    for (int k = 0; k < neuronsN; k++)
                    {
                        reader.ReadStartElement("neuron");
                        //reader.Read();
                        for (int i = 0; i < inputsN; i++)
                        {
                            reader.ReadStartElement("weight");
                            w[inputsN * k + i] = Convert.ToDouble(reader.ReadString());
                            reader.ReadEndElement();
                        }

                        reader.ReadEndElement(); //neuron
                    }
                    InitLayer(neuronsN, w);
                    reader.ReadEndElement(); //layer
                }
                reader.ReadEndElement();
                _numOfInputsGlobal = all;
                reader.Close();
            }
            catch (Exception e)
            {
                Console.WriteLine("Parsing problem: " + e.Message);
                return false;
            }
            ready = true;
            _numOfOutputs = Net[_numOfLayers-1].Count;
            Outputs = new double[_numOfOutputs];
            
            return true;
            
        }

        /// <summary>
        /// Sets the main network inputs for training.
        /// </summary>
        /// <param name="inputVector">Input values</param>
        /// <returns>false, if the network isn't initialized or the number of input values doesn't match the number of inputs</returns>
        public bool SetInputs(params double[] inputVector) 
        {
            if (!ready) return false;
            if (inputVector.Length != _numOfInputs) return false;
            inputVector.CopyTo(Inputs,0);
            //if (exec) return Execute();
            return true;
        }

        public  void SetWeightsGlobal(params double[] allWeights) 
        {
            int position=0;
            int neuronsN=0;
            for (int j = 0; j < _numOfLayers; j++)
            {
                neuronsN = Net[j].Count;
                for (int k = 0; k < neuronsN; k++)
                {
                    for (int i = 0; i < Net[j][k].NumberOfInputs; i++)
                    {
                        if (position == allWeights.Length) return;
                        Net[j][k].WeightChange(i, allWeights[position]);
                        position++;
                    }

                }
            }
        }

        public bool InitGenetic(int population, int best) 
        {
            if (numOfOutputs != 1) return false;
            if (best >= population) return false;
           
            this.best = best;
            this.population = population;

            for (int i = 0; i < population; i++)
            {
                GeneticPopulation.Add(new NeuralGen(numOfInputsGlobal));
                GeneticPopulation[i].RandomizeWeights(-10.0, 10.0);
            }
            return true;
        }

        public bool ApplyBestGenetic(int iterations)
        {
            if (numOfOutputs != 1) return false; 
           for(int i=0;i<iterations;i++)
           {
               for(int j=0;j<population;j++)
               {
                   SetWeightsGlobal(GeneticPopulation[j].getWeights());
                   Stimulate();
                   GeneticPopulation[j].Output = Outputs[0];
               }

              GeneticPopulation.Sort();
               int howmany =GeneticPopulation.Count-best;
              GeneticPopulation.RemoveRange(0,howmany);
              
              for (int k = 0; k < howmany; k++) 
              {
                  //todo: crossover 
                  // repopulating
                  
              }
           }
            return true;
        }

        /// <summary>
        ///  Teaches the network. SetInputs must be used first.
        /// </summary>
        /// <param name="exp">List of pairs representing the range in which subsequent outputs should be. 
        /// Format: (expLowerForOutput0,expUpperForOutput0,expLowerForOutput1,...)</param>
        /// <returns>false if the network is not initialized</returns>
        public bool StimulateAndTeach(params double [] exp)
        {
           if(!ready) return false;
           for (int j = 0; j < Net[0].Count; j++) 
           {
                  Net[0][j].Stimulate(Inputs);
           }
           for (int i = 1; i < numOfLayers; i++) 
           {
               double [] prev = GetOutputs(i - 1);
               for (int j = 0; j < Net[i].Count; j++) 
               {               
                   Net[i][j].Stimulate(prev);
               }
           }
           GetOutputs();
            int ErrorState=0;
            double [] err = new double[_numOfOutputs];
           for (int k = 0; k < _numOfOutputs; k++)
           {
               /*
               if ((Outputs[k] < exp[k * 2]) || (Outputs[k] > exp[k * 2 + 1]))
               {
                   ErrorState = 1;
                   err[k] = 0.5 * exp[k * 2] + 0.5 * exp[k * 2 + 1] - Outputs[k];
               }

               */
               if (Outputs[k] < exp[k * 2])
               {
                   err[k] = exp[k * 2+1] - Outputs[k];

                   ErrorState = 1;

               }
               else if (Outputs[k] > exp[k * 2 + 1])
               {
                   err[k] = exp[k * 2] - Outputs[k] ;

                   ErrorState = 1;
               } 
           }

           for (int i = numOfLayers - 1; i > 0; i--)
           {
               for (int j = 0; j < Net[i].Count; j++)
               {
                   if (i == numOfLayers - 1)
                   {
                       Net[i][j].ErrorRate = err[j];
                       
                   }
                   for (int k = 0; k < Net[i - 1].Count; k++)
                   {
                       if (j == 0)
                           Net[i - 1][k].ErrorRate = Net[i][j].GetWeight(k) * Net[i][j].ErrorRate;
                       else
                           Net[i - 1][k].ErrorRate += Net[i][j].GetWeight(k) * Net[i][j].ErrorRate;
                   }
                      
               }
           }

           for (int i = 0; i < numOfLayers; i++)
           {
               for (int j = 0; j < Net[i].Count; j++)
               {
                   Net[i][j].TeachPropagation(ErrorState);
                   if (i == 0)
                       Net[i][j].InputChange(Inputs);
                   else
                       Net[i][j].InputChange(GetOutputs(i - 1));

               }
           }

           return true;
        }

        /// <summary>
        /// Calculate the output without teaching
        /// </summary>
        /// <param name="inputsAlt">Input to calculate output for</param>
        /// <returns>array of outputs</returns>
        public double [] Stimulate(params double[] inputsAlt)
        {
            if (!ready) return null;
            for (int j = 0; j < Net[0].Count; j++)
            {
                if (inputsAlt.Length != numOfInputs)
                    Net[0][j].Stimulate(Inputs);
                else
                    Net[0][j].Stimulate(inputsAlt);
            }
            for (int i = 1; i < numOfLayers; i++)
            {
                double[] prev = GetOutputs(i - 1);
                for (int j = 0; j < Net[i].Count; j++)
                {

                    Net[i][j].Stimulate(prev);
                }
            }
            return GetOutputs();
            
        }


        /// <summary>
        ///  Gets the current outpur
        /// </summary>
        /// <returns>network output</returns>
        public double[] GetOutputs() 
        {            
            if(!ready) return null;
            if (numOfOutputs == 0) return null;
            double[] ret = new double[numOfOutputs];
            int last = numOfLayers-1;
            for (int i = 0; i < Net[last].Count; i++) 
            {
                ret[i] = Net[last][i].Output;
                Outputs[i] = Net[last][i].Output;
            }
            return ret;
        }

        /// <summary>
        /// Conversion of the output to array of double
        /// </summary>
        /// <param name="nn">Object of NeuralNet</param>
        /// <returns>array of double containing the output of the network</returns>
        public static implicit operator double [] (NeuralNet nn) 
        {
            if (nn.ready)
                return nn.GetOutputs();
            //else
            return null;
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="nn"></param>
        /// <returns></returns>
        public static implicit operator double(NeuralNet nn)
        {
            if (nn.ready)
                return nn.GetOutputs()[0];
            //else
            return 0.0;
        }

        /// <summary>
        ///  Private method to retrieve the output of an inner layer. For a hidden, and last layer - the input is the previous layers output.
        /// </summary>
        /// <param name="fromLayer"></param>
        /// <returns></returns>
        private double[] GetOutputs(int fromLayer)
        {
            if(!ready) return null;
            if (numOfOutputs == 0) return null;
            if (fromLayer >= numOfLayers) return null;

            double[] ret = new double[Net[fromLayer].Count];
            for (int i = 0; i < Net[fromLayer].Count; i++)
            {
                ret[i] = Net[fromLayer][i].Output;
            }
            return ret;
        }



        /// <summary>
        /// Inits layers with given number of neurons in a layer
        /// </summary>
        /// <param name="neuronsNum">array containing the number of neurons in particular layers</param>
        /// <returns>false, if the network is already initialized</returns>
        public bool InitLayers(params int [] neuronsNum)
        {
           if(ready) return false;
            if (neuronsNum.Length != numOfLayers) return false;
            int inputNum;
            int all=0;
            _numOfOutputs = neuronsNum[neuronsNum.Length - 1];
            Outputs = new double[_numOfOutputs];
            for(int i = 0; i< neuronsNum.Length; i++)
            {
               for (int j = 0; j < neuronsNum[i]; j++) 
               {
                   if(i == 0) inputNum = _numOfInputs;
                   else inputNum = neuronsNum[i-1];
                   all += inputNum;
                   Net[i].Add(new Neuron(inputNum,-10,10));
               }
            }
            _numOfInputsGlobal = all;
            ready = true;
            return true;
        }

        /// <summary>
        ///    Inits next layer of the Neural net 
        ///    up to the defined capacity in the constructor
        /// </summary>
        /// <param name="neuronsNum">Number of Neurons in the subsequent layer</param>
        /// <returns>false if all the layers are defined
        /// true if else
        /// </returns>
        public bool InitLayer(int neuronsNum, params double[] weights)
        {
            if(ready) return false;
            if (actNumOfLayers == _numOfLayers) return false;
            int inputNum;
            for (int j = 0; j < neuronsNum; j++) 
            {
               if (actNumOfLayers == 0) inputNum = _numOfInputs;
               else inputNum = Net[actNumOfLayers - 1].Count;

               if(weights.Length!=0)
               {
                  Net[actNumOfLayers].Add(new Neuron(inputNum));
                  for(int k=0;k<inputNum;k++)
                  {
                     Net[actNumOfLayers][j].WeightChange(k,weights[j*inputNum+k]);
                  }
               }
               else
               {
                  Net[actNumOfLayers].Add(new Neuron(inputNum, -10,10));
               }
            }
            if (actNumOfLayers == (_numOfLayers - 1) )
            { 
                _numOfOutputs = neuronsNum;
                Outputs = new double[_numOfOutputs];
                ready = true;
            }
            actNumOfLayers++;
            return true;
        }

        /// <summary>
        /// Inits a subsequent input.
        /// </summary>
        /// <param name="inputVal">The value of the input</param>
        /// <returns>false, if the network is initialized, or all the inputs are set</returns>
        private bool InitInput(double inputVal)
        {
            if (!ready) return false;
            if (actNumOfInputValues == _numOfInputs) return false;
            
            actNumOfInputValues++;
            return true;
        }


        public static NeuralNet operator <<(NeuralNet nt, int inputVal)
        {
            nt.InitInput(inputVal);
            return nt;
        }

        /// <summary>
        ///  Clears all inputs to 0;
        /// </summary>
        public void ClearInputs() 
        {
            actNumOfInputValues = 0;
            for (int i = 0; i < numOfInputs; i++) Inputs[i] = 0.0;
        }

    }
    #endregion

    #region NNStockMarket

    public class NNStockMarket : NeuralNet
    {
        private double PreviousPrize;
        private double Change;
        public double getChange 
        {
            get { return Change; }
        }
        private double Decision;
        public double getDecision
        {
            get { return Decision; }
        }
        private int IndicatorPairs; //inputs;
        private int NetworkDepth;
        private double _BestEffort;
        private double _initPrize;
        public double initPrize 
        {
            get { return _initPrize; }
            set
            {
                    if ((_initPrize < 0) && (value > 0))
                    {
                        PreviousPrize = value;
                        _initPrize = value;
                    }
            }
        }
        public double BestEffort
        {
           get {return _BestEffort;}
           set 
           {
              if(value <0.0) _BestEffort=double.Epsilon;
              else if(value > 1.0) _BestEffort=1.0;
              else _BestEffort = value;
           }
        }
        private double _WorstEffort;
        public double WorstEffort
        {
           get {return _WorstEffort;}
           set 
           {
              if(value <-1.0) _WorstEffort=-1.0;
              else if(value > 0.0) _WorstEffort=-double.Epsilon;
              else _WorstEffort=value; 
           }
        }
        private double _Sensitivity;
        public double Sensitivity
        {
            get { return _Sensitivity; }
            set
            {
                if (value < 0.0) _Sensitivity= 0.0;
                else if (value > 1.0) _Sensitivity = 1.0;
                else _Sensitivity = value;
            }
        }

        public NNStockMarket(int indicatorPairsNum, int NetworkDepthNum)
            : base(indicatorPairsNum, NetworkDepthNum)
        {
            PreviousPrize = 0;
            Change = 0;
            IndicatorPairs = indicatorPairsNum;
            NetworkDepth = NetworkDepthNum;
            _WorstEffort = -0.3;
            _BestEffort  = 0.3;
            _Sensitivity = 0.2;
            _initPrize = -1.0;
            for(int i=0;i<this.numOfLayers;i++)
            {
               this.InitLayer(this.numOfLayers-i);
            }
        }

        public NNStockMarket(String FileName) : base(FileName)
        {
            NetworkDepth = base.numOfLayers;
            _WorstEffort = -0.3;
            _BestEffort = 0.3;
            _Sensitivity = 0.2;
            _initPrize = -1.0;
            PreviousPrize = 0;
            Change = 0;
            IndicatorPairs = numOfInputs;
        }


        /// <summary>
        ///  Teaches the neural network dedicated for stock market.
        /// </summary>
        /// <param name="prize">the current prize</param>
        /// <param name="inputsVec">the vector of the indicator pairs (the value)</param>
        /// <returns>NaN if the first prize hasn't been initialized - use InitPrize atribute.
        /// Else: the value of the decision <-1.0;1.0></returns>
        public double Teach(double prize, double futurePrize, params double[] inputsVec)
        {

            for (int i = 0; i < inputsVec.Length; i++) 
            {
                inputsVec[i] = inputsVec[i] * 2 - 1;
            }

            //if (initPrize < 0) { initPrize = prize; return double.NaN; }
            double r = (futurePrize-prize)/prize; // the difference between the future prize and current in relation to the current
            Change = r;
            double ub, lb;
            PreviousPrize=prize;

            if (r < 0) //we've lost
            {
                lb = -(r / WorstEffort);
                if (lb < -1.0) lb = -1.0;
                ub = lb + Sensitivity;
            }
            else if (r > 0) //we've won
            {
                ub = r / BestEffort;
                if (ub > 1.0) ub = 1.0;
                lb = ub - Sensitivity;
            }
            else //it should be in the middle
            {
                lb = -Sensitivity * 0.5;
                ub =  Sensitivity * 0.5; 
            }

            base.SetInputs(inputsVec);
            base.StimulateAndTeach(lb, ub);
            Decision = this;
            return Decision;
        }

        public double MakeDecision(params double[] inputVec) 
        {
            for (int i = 0; i < inputVec.Length; i++)
            {
                inputVec[i] = inputVec[i] * 2 - 1;
            }
            Decision = base.Stimulate(inputVec)[0];
            return Decision;
        }
         
    }
   
    #endregion
}